﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace ResizeDragBehaviorWPF
{
    /// <summary>
    /// Overlays a control with the specified content
    /// </summary>
    /// <typeparam name="TOverlay">The type of content to create the overlay from</typeparam>
    public class OverlayAdorner<TOverlay> : Adorner, IDisposable where TOverlay : FrameworkElement, new()
    {
        /// <summary>
        /// Overlay the specified element
        /// </summary>
        /// <param name="elementToAdorn">The element to overlay</param>
        /// <returns></returns>
        public static IDisposable Overlay(FrameworkElement elementToAdorn)
        {
            return Overlay(elementToAdorn, new TOverlay());
        }

        /// <summary>
        /// Overlays the element with the specified instance of TOverlay
        /// </summary>
        /// <param name="elementToAdorn">Element to overlay</param>
        /// <param name="adorningElement">The content of the overlay</param>
        /// <returns></returns>
        public static IDisposable Overlay(FrameworkElement elementToAdorn, TOverlay adorningElement)
        {
            var adorner = new OverlayAdorner<TOverlay>(elementToAdorn, adorningElement);

            adorner.LoadingDelegate = delegate(object sender, RoutedEventArgs e)
            {
                if (adorner.ToBeDisposed)
                {
                    adorner.Dispose();
                }
                else if (adorner.Layer != null && !adorner.Added)
                {
                    adorner.Layer.Add(adorner);
                    adorner.Added = true;
                }
            };

            adorner.UnloadingDelegate = delegate(object sender, RoutedEventArgs e)
            {
                if (adorner.Layer != null)
                {
                    adorner.Layer.Remove(adorner);
                    adorner.Added = false;
                }
            };

            if (elementToAdorn.IsLoaded)
            {
                if (adorner.Layer != null)
                {
                    adorner.Layer.Add(adorner);
                    adorner.Added = true;
                }
            }

            elementToAdorn.Loaded += adorner.LoadingDelegate;
            elementToAdorn.Unloaded += adorner.UnloadingDelegate;


            return adorner as IDisposable;
        }



        private FrameworkElement adorningElement;
        private FrameworkElement elementToAdorn;
        private AdornerLayer layer;
        private RoutedEventHandler loadingDelegate;
        private RoutedEventHandler unloadingDelegate;
        private bool added = false;
        private bool toBeDisposed = false;

        private OverlayAdorner(FrameworkElement elementToAdorn, FrameworkElement adorningElement)
            : base(elementToAdorn)
        {
            AdorningElement = adorningElement;
            ElementToAdorn = elementToAdorn;
            if (AdorningElement != null)
                AddVisualChild(AdorningElement);

            Focusable = true;
        }

        public FrameworkElement AdorningElement
        {
            get { return adorningElement; }
            set
            {
                if (!FrameworkElement.Equals(adorningElement, value))
                {
                    adorningElement = value;
                }
            }
        }

        public FrameworkElement ElementToAdorn
        {
            get { return elementToAdorn; }
            set
            {
                if (!FrameworkElement.Equals(elementToAdorn, value))
                {
                    elementToAdorn = value;
                }
            }
        }

        public AdornerLayer Layer
        {
            get
            {
                if (layer == null)
                    layer = AdornerLayer.GetAdornerLayer(ElementToAdorn);

                return layer;
            }
            set
            {
                if (!AdornerLayer.Equals(layer, value))
                {
                    layer = value;
                }
            }
        }

        public bool Added
        {
            get { return added; }
            set
            {
                if (!bool.Equals(added, value))
                {
                    added = value;
                }
            }
        }

        public bool ToBeDisposed
        {
            get { return toBeDisposed; }
            set
            {
                if (!bool.Equals(toBeDisposed, value))
                {
                    toBeDisposed = value;
                }
            }
        }

        public RoutedEventHandler LoadingDelegate
        {
            get { return loadingDelegate; }
            set
            {
                if (!RoutedEventHandler.Equals(loadingDelegate, value))
                {
                    loadingDelegate = value;
                }
            }
        }

        public RoutedEventHandler UnloadingDelegate
        {
            get { return unloadingDelegate; }
            set
            {
                if (!RoutedEventHandler.Equals(unloadingDelegate, value))
                {
                    unloadingDelegate = value;
                }
            }
        }

        protected override int VisualChildrenCount
        {
            get { return adorningElement == null ? 0 : 1; }
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (adorningElement != null)
            {
                Point adorningPoint = new Point(0, 0);
                adorningElement.Arrange(new Rect(adorningPoint, this.AdornedElement.RenderSize));
            }
            return finalSize;
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index == 0 && adorningElement != null)
            {
                return adorningElement;
            }
            return base.GetVisualChild(index);
        }

        public void Dispose()
        {
            ToBeDisposed = true;

            if (Layer != null)
            {
                if (LoadingDelegate != null)
                    elementToAdorn.Loaded -= LoadingDelegate;

                if (UnloadingDelegate != null)
                    elementToAdorn.Unloaded -= UnloadingDelegate;

                RemoveVisualChild(adorningElement);
                AdorningElement = null;
                Layer.Remove(this);
            }
        }
    }
}
